﻿using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using UtilZ.Dotnet.Ex.Base;
using UtilZ.Dotnet.Ex.ZLog;
using UtilZ.Dotnet.Ex.Communication.Net.FastTransfer;
using UtilZ.Dotnet.Ex.Communication.Net.Base;
using UtilZ.Dotnet.Ex.Communication.Base;

namespace UtilZ.Dotnet.Ex.Communication.Net.Telnet
{
    internal partial class ListenTelnetClient : IListenTelnetClient
    {

        private readonly byte[] _buffer;

        private readonly Socket _socket;
        public Socket Socket
        {
            get
            {

                return this._socket;
            }
        }

        /// <summary>
        /// telnet连接状态改变事件
        /// </summary>
        public event EventHandler<NetConnectedChangedArgs> ConnectChanged;

        private readonly Func<TelnetMessageArgs, string> _processMessage;
        private readonly Action<ListenTelnetClient, DisconnectMode> _onDisconnect;
        private readonly Func<string, TelnetUserInfo> _checkAccount;

        /// <summary>
        /// telnet客户端连接状态
        /// </summary>
        public ConnectionStatus Status { get; private set; }

        private void OnRaiseDisconnect(DisconnectMode disconnectMode)
        {
            this._onDisconnect(this, disconnectMode);
            this.Status = ConnectionStatus.Closed;
            this.ConnectChanged?.Invoke(this, new NetConnectedChangedArgs(ConnectionStatus.Closed, disconnectMode, this._linkInfo.LocalEndPoint, this._linkInfo.RemoteEndPoint));
        }

        private readonly NetLinkInfo _linkInfo;
        private readonly string _connectedPromptMessage;
        private Encoding _messageEncoding;

        private readonly ThreadEx _receivedDataThread;


        internal ListenTelnetClient(Socket socket, Action<ListenTelnetClient, DisconnectMode> onDisconnect, Encoding messageEncoding, Func<TelnetMessageArgs, string> processMessage, string connectedPromptMessage)
            : this(socket, onDisconnect, null, messageEncoding, processMessage, connectedPromptMessage)
        {
            this._authed = true;
        }

        internal ListenTelnetClient(Socket socket, Action<ListenTelnetClient, DisconnectMode> onDisconnect, Func<string, TelnetUserInfo> checkAccount, Encoding messageEncoding,
            Func<TelnetMessageArgs, string> processMessage, string connectedPromptMessage)
        {
            this._linkInfo = new NetLinkInfo(socket.RemoteEndPoint, socket.LocalEndPoint);
            this.Status = ConnectionStatus.Opened;
            this._buffer = new byte[TransferConstant.MTU_MAX];
            this._socket = socket;
            this._onDisconnect = onDisconnect;
            this._checkAccount = checkAccount;

            if (messageEncoding == null)
            {
                this._messageEncoding = Encoding.UTF8;
            }
            else
            {
                this._messageEncoding = messageEncoding;
            }

            this._processMessage = processMessage;
            this._connectedPromptMessage = connectedPromptMessage;


            string ipStr = NetHelper.GetIPString(socket.RemoteEndPoint);
            this._receivedDataThread = new ThreadEx(this.ReceivedDataThreadMethod, $"{ipStr}_接收数据线程", true);
            this._receivedDataThread.Start();
        }

        public void Dispose()
        {
            try
            {
                this._receivedDataThread.Stop();
                try
                {
                    this._socket.Disconnect(false);
                    this._socket.Shutdown(SocketShutdown.Both);
                    this._socket.Close();
                    this._socket.Dispose();
                }
                catch (ObjectDisposedException)
                {
                }

                this._receivedDataThread.Dispose();
            }
            catch (Exception ex)
            {
                ZLoger.Error(ex);
            }
        }





        private void ReceivedDataThreadMethod(ThreadPara threadPara)
        {
            try
            {
                Socket client = this._socket;
                byte[] echoBytes = GetEchoBytes();
                this.Send(echoBytes);

                byte[] data = this._buffer;
                int revicedDataLength;

                while (!threadPara.Token.IsCancellationRequested)
                {
                    try
                    {
                        revicedDataLength = client.Receive(data, 0, data.Length, SocketFlags.None);
                        if (revicedDataLength > 0)
                        {
                            this.ProcessReceiveData(data, revicedDataLength);
                        }
                        else
                        {
                            client.Close();
                            this.OnRaiseDisconnect(DisconnectMode.Passive);
                            break;
                        }
                    }
                    catch (SocketException sex)
                    {
                        if (NetHelper.SocketErrorIsDisconnect(sex.SocketErrorCode))
                        {
                            this.OnRaiseDisconnect(DisconnectMode.Passive);
                        }
                        else
                        {
                            ZLoger.Error(sex, "接收数据发生未处理到的异常");
                        }
                        break;
                    }
                    catch (ObjectDisposedException)
                    {
                        break;
                    }
                    catch (ThreadAbortException)
                    {
                        break;
                    }
                    catch (Exception ex)
                    {
                        ZLoger.Error(ex, "接收数据异常");
                    }
                }
            }
            catch (ThreadAbortException)
            { }
            catch (Exception ex)
            {
                ZLoger.Error(ex);
            }
        }




        private static byte[] _echoBytes = null;
        private static byte[] GetEchoBytes()
        {
            if (_echoBytes == null)
            {
                _echoBytes = new byte[]
                {
                    0xff, 0xfd, 0x01,   // Do Echo
                    0xff, 0xfd, 0x21,   // Do Remote Flow Control
                    0xff, 0xfb, 0x01,   // Will Echo
                    0xff, 0xfb, 0x03    // Will Supress Go Ahead
                 };
            }

            return _echoBytes;
        }
    }
}
